/*
 * Copyright 2002-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.tests;

import java.util.List;

import org.mockito.Mockito;
import org.mockito.internal.stubbing.InvocationContainerImpl;
import org.mockito.internal.util.MockUtil;
import org.mockito.invocation.Invocation;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

/**
 * General test utilities for use with {@link Mockito}.
 *
 * @author Phillip Webb
 */
public abstract class MockitoUtils {

    /**
     * Verify the same invocations have been applied to two mocks. This is generally not
     * the preferred way test with mockito and should be avoided if possible.
     *
     * @param expected         the mock containing expected invocations
     * @param actual           the mock containing actual invocations
     * @param argumentAdapters adapters that can be used to change argument values before they are compared
     */
    public static <T> void verifySameInvocations(T expected, T actual, InvocationArgumentsAdapter... argumentAdapters) {
        List<Invocation> expectedInvocations =
                ((InvocationContainerImpl) MockUtil.getMockHandler(expected).getInvocationContainer()).getInvocations();
        List<Invocation> actualInvocations =
                ((InvocationContainerImpl) MockUtil.getMockHandler(actual).getInvocationContainer()).getInvocations();
        verifySameInvocations(expectedInvocations, actualInvocations, argumentAdapters);
    }

    private static void verifySameInvocations(List<Invocation> expectedInvocations, List<Invocation> actualInvocations,
                                              InvocationArgumentsAdapter... argumentAdapters) {

        assertThat(expectedInvocations.size(), is(equalTo(actualInvocations.size())));
        for (int i = 0; i < expectedInvocations.size(); i++) {
            verifySameInvocation(expectedInvocations.get(i), actualInvocations.get(i), argumentAdapters);
        }
    }

    private static void verifySameInvocation(Invocation expectedInvocation, Invocation actualInvocation,
                                             InvocationArgumentsAdapter... argumentAdapters) {

        assertThat(expectedInvocation.getMethod(), is(equalTo(actualInvocation.getMethod())));
        Object[] expectedArguments = getInvocationArguments(expectedInvocation, argumentAdapters);
        Object[] actualArguments = getInvocationArguments(actualInvocation, argumentAdapters);
        assertThat(expectedArguments, is(equalTo(actualArguments)));
    }

    private static Object[] getInvocationArguments(Invocation invocation, InvocationArgumentsAdapter... argumentAdapters) {
        Object[] arguments = invocation.getArguments();
        for (InvocationArgumentsAdapter adapter : argumentAdapters) {
            arguments = adapter.adaptArguments(arguments);
        }
        return arguments;
    }


    /**
     * Adapter strategy that can be used to change invocation arguments.
     */
    public interface InvocationArgumentsAdapter {

        /**
         * Change the arguments if required.
         *
         * @param arguments the source arguments
         * @return updated or original arguments (never {@code null})
         */
        Object[] adaptArguments(Object[] arguments);
    }

}
